"""
Type Resolver

Core functionality for resolving C++ template instantiations and extracting
concrete types from AST cursors.
"""

import re
from typing import Optional, List, Dict, Any, Tuple, Set
from dataclasses import dataclass
from enum import Enum

# Don't import clang at module level - import it when needed in methods
CLANG_AVAILABLE = None  # Will be determined at runtime


class TemplateArgumentType(Enum):
    """Types of template arguments."""
    TYPE = "Type"
    INTEGRAL = "Integral"
    NULL = "Null"
    DECLARATION = "Declaration"
    NULLPTR = "NullPtr"
    TEMPLATE = "Template"
    TEMPLATE_EXPANSION = "TemplateExpansion"
    EXPRESSION = "Expression"
    PACK = "Pack"


@dataclass
class ResolvedType:
    """Represents a resolved C++ type with template information."""
    base_type: str
    template_args: List['ResolvedType'] = None
    is_template: bool = False
    is_pointer: bool = False
    is_reference: bool = False
    is_const: bool = False
    namespace: Optional[str] = None
    
    def __post_init__(self):
        if self.template_args is None:
            self.template_args = []
    
    def to_string(self) -> str:
        """Convert resolved type to human-readable string."""
        result = self.base_type
        
        if self.is_template and self.template_args:
            arg_strings = [arg.to_string() for arg in self.template_args]
            result += f"<{', '.join(arg_strings)}>"
        
        if self.is_const:
            result = f"const {result}"
        
        if self.is_reference:
            result += "&"
        elif self.is_pointer:
            result += "*"
        
        return result
    
    def get_canonical_name(self) -> str:
        """Get the canonical (fully qualified) type name."""
        name = self.base_type
        if self.namespace:
            name = f"{self.namespace}::{name}"
        
        if self.is_template and self.template_args:
            arg_names = [arg.get_canonical_name() for arg in self.template_args]
            name += f"<{', '.join(arg_names)}>"
        
        return name


class TypeResolver:
    """Resolves C++ template instantiations and extracts concrete types."""
    
    def _is_clang_available(self):
        """Check if clang is available at runtime."""
        try:
            # Always try fresh import - don't cache failures since libclang might be configured later
            from clang.cindex import Index
            # Test that we can actually create an index (this will fail if libclang isn't configured)
            Index.create()
            return True
        except Exception:
            return False
    
    def __init__(self):
        # Check clang availability at runtime
        if not self._is_clang_available():
            error_msg = "libclang is not available. Please ensure libclang is configured before importing TIR modules.\n"
            error_msg += "Run 'python -m tir.libclang_config' to check configuration status."
            raise ImportError(error_msg)
        
        # Import clang types now that we know they're available
        # Import clang types - some might not be available in all versions
        from clang.cindex import Cursor, CursorKind, Type, TypeKind
        # Try to import template-related types (might not be available)
        try:
            from clang.cindex import TemplateArgument, TemplateArgumentKind
        except ImportError:
            # Template argument support not available in this clang version
            TemplateArgument = None
            TemplateArgumentKind = None
        
        # Cache for resolved types to avoid redundant processing
        self._type_cache: Dict[str, ResolvedType] = {}
        
        # Known type aliases for better output
        self.type_aliases = {
            'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>': 'std::string',
            'std::basic_string<char>': 'std::string',
            'std::basic_string<wchar_t>': 'std::wstring',
        }
    
    def resolve_cursor_type(self, cursor: "Cursor") -> Optional[ResolvedType]:
        """Resolve the type of a cursor (variable, parameter, etc.)."""
        if not cursor or not cursor.type:
            return None
        
        return self.resolve_type(cursor.type)
    
    def resolve_type(self, clang_type: "Type") -> Optional[ResolvedType]:
        """Resolve a clang Type object to ResolvedType."""
        if not clang_type:
            return None
        
        # Check cache first
        type_key = self._get_type_cache_key(clang_type)
        if type_key in self._type_cache:
            return self._type_cache[type_key]
        
        # Resolve the type
        resolved = self._resolve_type_internal(clang_type)
        
        # Cache the result
        if resolved and type_key:
            self._type_cache[type_key] = resolved
        
        return resolved
    
    def _resolve_type_internal(self, clang_type: "Type") -> Optional[ResolvedType]:
        """Internal type resolution logic."""
        if not clang_type:
            return None
        
        # Import TypeKind at runtime
        from clang.cindex import TypeKind
        
        # Get canonical type for accurate analysis
        canonical_type = clang_type.get_canonical()
        
        # Extract basic type information
        base_spelling = clang_type.spelling
        is_const = clang_type.is_const_qualified()
        is_pointer = (clang_type.kind == TypeKind.POINTER)
        is_reference = (clang_type.kind == TypeKind.LVALUEREFERENCE or 
                       clang_type.kind == TypeKind.RVALUEREFERENCE)
        
        # Handle pointer/reference types by getting pointed-to type
        actual_type = clang_type
        if is_pointer or is_reference:
            actual_type = clang_type.get_pointee()
        
        # Resolve template instantiations
        template_args = []
        is_template = False
        
        if self._is_template_instantiation(actual_type):
            is_template = True
            template_args = self._extract_template_arguments(actual_type)
        
        # Clean up the base type name
        base_type = self._clean_type_name(actual_type.spelling)
        
        # Extract namespace information
        namespace = self._extract_namespace(actual_type)
        
        resolved_type = ResolvedType(
            base_type=base_type,
            template_args=template_args,
            is_template=is_template,
            is_pointer=is_pointer,
            is_reference=is_reference,
            is_const=is_const,
            namespace=namespace
        )
        
        return self._apply_type_aliases(resolved_type)
    
    def _is_template_instantiation(self, clang_type: "Type") -> bool:
        """Check if a type is a template instantiation."""
        try:
            # Method available in newer libclang versions
            if hasattr(clang_type, 'get_num_template_arguments'):
                return clang_type.get_num_template_arguments() > 0
            
            # Fallback: check if the spelling contains template syntax
            return '<' in clang_type.spelling and '>' in clang_type.spelling
        except:
            return False
    
    def _extract_template_arguments(self, clang_type: "Type") -> List[ResolvedType]:
        """Extract template arguments from a template instantiation."""
        template_args = []
        
        try:
            if hasattr(clang_type, 'get_num_template_arguments'):
                num_args = clang_type.get_num_template_arguments()
                
                for i in range(num_args):
                    try:
                        if hasattr(clang_type, 'get_template_argument_type'):
                            arg_type = clang_type.get_template_argument_type(i)
                            resolved_arg = self.resolve_type(arg_type)
                            if resolved_arg:
                                template_args.append(resolved_arg)
                        elif hasattr(clang_type, 'get_template_argument'):
                            # Handle different kinds of template arguments
                            arg = clang_type.get_template_argument(i)
                            resolved_arg = self._resolve_template_argument(arg)
                            if resolved_arg:
                                template_args.append(resolved_arg)
                    except Exception as e:
                        # If we can't resolve a specific argument, create a placeholder
                        template_args.append(ResolvedType(base_type=f"<unresolved_arg_{i}>"))
            
            else:
                # Fallback: parse template arguments from spelling
                template_args = self._parse_template_args_from_spelling(clang_type.spelling)
        
        except Exception as e:
            # Graceful fallback if template argument extraction fails
            template_args = self._parse_template_args_from_spelling(clang_type.spelling)
        
        return template_args
    
    def _resolve_template_argument(self, template_arg: Any) -> Optional[ResolvedType]:
        """Resolve a template argument (type, value, etc.)."""
        try:
            if hasattr(template_arg, 'kind'):
                if template_arg.kind == TemplateArgumentKind.TYPE:
                    return self.resolve_type(template_arg.as_type())
                elif template_arg.kind == TemplateArgumentKind.INTEGRAL:
                    # For integral template arguments, return the value as type
                    value = template_arg.as_integral()
                    return ResolvedType(base_type=str(value))
                elif template_arg.kind == TemplateArgumentKind.NULL:
                    return ResolvedType(base_type="nullptr")
            
            # Fallback: try to get as type
            if hasattr(template_arg, 'as_type'):
                return self.resolve_type(template_arg.as_type())
        
        except Exception:
            pass
        
        return ResolvedType(base_type="<unknown_template_arg>")
    
    def _parse_template_args_from_spelling(self, spelling: str) -> List[ResolvedType]:
        """Parse template arguments from type spelling (fallback method)."""
        template_args = []
        
        # Extract content between < and >
        match = re.search(r'<(.+)>$', spelling)
        if not match:
            return template_args
        
        args_str = match.group(1)
        
        # Simple parsing - doesn't handle all edge cases but works for common templates
        args = self._split_template_args(args_str)
        
        for arg in args:
            arg = arg.strip()
            if arg:
                # Try to detect if it's a type or value
                if arg.isdigit() or arg in ['true', 'false', 'nullptr']:
                    template_args.append(ResolvedType(base_type=arg))
                else:
                    # Recursively parse nested templates
                    if '<' in arg and '>' in arg:
                        nested_resolved = ResolvedType(base_type="<parsed_from_spelling>")
                        nested_resolved.is_template = True
                        # This is a simplified version - full recursive parsing would be complex
                        template_args.append(nested_resolved)
                    else:
                        template_args.append(ResolvedType(base_type=self._clean_type_name(arg)))
        
        return template_args
    
    def _split_template_args(self, args_str: str) -> List[str]:
        """Split template arguments string, handling nested templates."""
        args = []
        current_arg = ""
        depth = 0
        
        for char in args_str:
            if char == '<':
                depth += 1
                current_arg += char
            elif char == '>':
                depth -= 1
                current_arg += char
            elif char == ',' and depth == 0:
                args.append(current_arg.strip())
                current_arg = ""
            else:
                current_arg += char
        
        if current_arg.strip():
            args.append(current_arg.strip())
        
        return args
    
    def _clean_type_name(self, type_name: str) -> str:
        """Clean up type name by removing unnecessary qualifiers."""
        # Remove template arguments for base name
        base_name = re.sub(r'<.*>$', '', type_name)
        
        # Remove leading/trailing whitespace
        base_name = base_name.strip()
        
        # Remove unnecessary keywords
        base_name = re.sub(r'\bstruct\s+', '', base_name)
        base_name = re.sub(r'\bclass\s+', '', base_name)
        base_name = re.sub(r'\benum\s+', '', base_name)
        
        return base_name
    
    def _extract_namespace(self, clang_type: "Type") -> Optional[str]:
        """Extract namespace information from a type."""
        # This is a simplified extraction - full namespace resolution
        # would require AST traversal
        spelling = clang_type.spelling
        
        if '::' in spelling:
            parts = spelling.split('::')
            if len(parts) > 1:
                # Remove the last part (which is the type name)
                return '::'.join(parts[:-1])
        
        return None
    
    def _apply_type_aliases(self, resolved_type: ResolvedType) -> ResolvedType:
        """Apply known type aliases for cleaner output."""
        full_name = resolved_type.to_string()
        
        for alias_from, alias_to in self.type_aliases.items():
            if full_name == alias_from:
                resolved_type.base_type = alias_to
                resolved_type.template_args = []
                resolved_type.is_template = False
                break
        
        return resolved_type
    
    def _get_type_cache_key(self, clang_type: "Type") -> str:
        """Generate a cache key for a clang Type."""
        try:
            # Use canonical type spelling as cache key
            canonical = clang_type.get_canonical()
            return canonical.spelling + str(clang_type.kind.value)
        except:
            return clang_type.spelling + str(hash(str(clang_type)))
    
    def clear_cache(self):
        """Clear the type resolution cache."""
        self._type_cache.clear()
    
    def get_type_info(self, cursor: "Cursor") -> Dict[str, Any]:
        """Get comprehensive type information for a cursor."""
        if not cursor:
            return {}
        
        resolved_type = self.resolve_cursor_type(cursor)
        
        info = {
            'cursor_kind': cursor.kind.name,
            'cursor_spelling': cursor.spelling,
            'raw_type_spelling': cursor.type.spelling if cursor.type else None,
            'resolved_type': resolved_type.to_string() if resolved_type else None,
            'canonical_name': resolved_type.get_canonical_name() if resolved_type else None,
            'is_template': resolved_type.is_template if resolved_type else False,
            'template_arg_count': len(resolved_type.template_args) if resolved_type and resolved_type.template_args else 0
        }
        
        if resolved_type and resolved_type.is_template:
            info['template_args'] = [
                arg.to_string() for arg in resolved_type.template_args
            ]
        
        return info